package com.safety51.bootstrap.demo;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.lang.UUID;
import cn.hutool.crypto.asymmetric.KeyType;
import cn.hutool.crypto.asymmetric.RSA;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.JSONPath;
import com.google.common.collect.Maps;
import lombok.Data;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import lombok.var;
import org.apache.commons.lang3.StringUtils;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.http.*;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;

import java.time.LocalDateTime;
import java.util.*;

/**
 * Created by liuyuancheng on 2022/6/13  <br/>
 *
 * @author liuyuancheng
 */
@Slf4j
public class OAuth2Demo {

    private final RestTemplate restTemplate = new RestTemplate();

    private final String host = "https://acg.mj.sddz.95gps.cn:9915/";

    private static final String clientId = "37b08fce-52be-4986-9524-d0a1ebc219c8";

    private static final String clientSecret = "4c38f72f9d6a8c74f316cdeb78294c50";

    private static final String remoteRsaPublicKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCWdnQwfF+T6WsZEd8UyKPJO2Gj9vce+jN6bXNMIlw56318A/k6zY4O/wDsCp5x7lbwBcbDj8VFE1e7bh0ycib+ZEtFGJgyyuVjzXujy/agKucKmhrbtjLzt5xJk/+SPES+mnAZ3C5K8DOgCXozsKIyRvPngArfTo3DOD+8hK/7TQIDAQAB";

    private static final String localRsaPublicKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCyeAiEZ+xVo+1PGad2edU1HA0iYd/TtYw1D2bEaN7vJaEErEsJeZgAQnvfnEWUkJl7PE9AAQTbjuHjCfYgyC6wlYfW/7REbhWZZizwDUSslgpHkCKNF4eHz86vv+8wIl6I+6BoM9Tu6iXhrwL6iKyNYQ5OhNX07SjB+xuv05DrEwIDAQAB";

    private static final String localRsaPrivateKey = "MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBALJ4CIRn7FWj7U8Zp3Z51TUcDSJh39O1jDUPZsRo3u8loQSsSwl5mABCe9+cRZSQmXs8T0ABBNuO4eMJ9iDILrCVh9b/tERuFZlmLPANRKyWCkeQIo0Xh4fPzq+/7zAiXoj7oGgz1O7qJeGvAvqIrI1hDk6E1fTtKMH7G6/TkOsTAgMBAAECgYAWD3pWC5aOG35JCc3GelbdgsMo3dpSDB3xrR6x5piwDQGzVyzLVglw9sbjJCkBrUIblp/hNH8KOMY5T1DqSnRi0Kuzc0w3x62qzfhhiTP0y7p/ErCNyzTFx1kfLt1ViS6pSfVFXGvY3A/AH0/tLjJ80M437v1IYPPUulYEVEOdCQJBANvjQnU00mUsJCBCodQVWB4pWta+XzVvmubU0gCo1GX/kyRa6RIK+RIuCsOlpm95DajRFlSAweA4n5/pT+hsUx8CQQDPx2aX2A1jh5MNgGT9QPafh1ePuKpFvseFsFBVPKz/U8pOhB6p62q9HpuycxYHtGYtQbcPdf1aWpuy7efjj32NAkA9CKy2uc24lXMFnVkr9Cy8WoBDYR4uR+c4MVSFKFekZDdkfPN6tZ8DA9KrEbzL7k7zT+4h0IeABFeSXIF4ozDDAkAQLBgLJ5DbN1k1ytGReLX5csJY+GmgZhoHiNKn38IjWXU8qUHtvdogADrkJ9sGHH97dEr4VBm1J6pIwgWzi56dAkBrhVfbsEyGH8srIn+GHe1hZhpW5khuTETQz5WNbS/yUkSiVSX1dEQQI5TA8sgvcYEUbvtZufkqQqnMZtPTb9O+";

    private volatile AuthTokenDTO authTokenDTO;

    private static final RSA remoteRsa = new RSA("RSA/ECB/PKCS1Padding", null, remoteRsaPublicKey);

    private static final String SOURCE_CODE = "3714260005";

    private String auth = "bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsiYXBwX3Jlc291cmNlX2lkIl0sImNsaWVudElkIjoiMzdiMDhmY2UtNTJiZS00OTg2LTk1MjQtZDBhMWViYzIxOWM4Iiwic2NvcGUiOlsiYXBwIl0sImV4cCI6MTY1NTQ3NjEwNSwiYXV0aG9yaXRpZXMiOlsib2F1dGgyIl0sImp0aSI6ImMzYTA5Mjk1LWVkZGMtNDBhMi05MGMzLTlmMzIxY2ZhM2JiNCIsImNsaWVudF9pZCI6IjM3YjA4ZmNlLTUyYmUtNDk4Ni05NTI0LWQwYTFlYmMyMTljOCJ9.YDXrLaB6fdEYVp_kl2r43L5DYkmr6xHrOR-JLng1PMefQMmyHIluOYgBixoRUKIi3f6M-uQfJgpwde7sMQVKBWD7GaZddcUshFBF1r3_Kvne_frcHiimcvJKKLhWgRIfb45jKNXpIKrA2pW6AlAh-nlaZ0K39QH9qhjJwcdPr_o";

    @Data
    static class AuthTokenDTO {
        private Long expiresIn;
        private String expiresSecond;
        private String accessToken;
        private String scope;
        private String tokenType;
    }

    private synchronized AuthTokenDTO acquireToken() {
        if (Objects.nonNull(authTokenDTO)) {
            return authTokenDTO;
        }
        var sysParams = new LinkedMultiValueMap<String, String>();
        sysParams.set("client_id", this.clientId);
        sysParams.set("client_secret", this.clientSecret);
        sysParams.set("grant_type", "client_credentials");
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
        HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(sysParams, headers);
        final ResponseEntity<String> responseString = restTemplate
                .postForEntity(StringUtils.join(host, "oauth/token"),
                        request,
                        String.class
                );
        if (responseString.getStatusCode() == HttpStatus.OK && JSONPath.read(responseString.getBody(), "$.code").equals("0")) {
            this.authTokenDTO = JSONObject.parseObject(JSONPath.read(responseString.getBody(), "$.data").toString(), AuthTokenDTO.class);
            return authTokenDTO;
        } else {
            log.error("鉴权失败：{}", responseString);
            return null;
        }
    }

    /**
     * 如果即将到期，则刷新token TODO
     */
    private String getAuthorization() {
//        return this.auth;
        if (Objects.isNull(this.authTokenDTO) || this.authTokenDTO.getExpiresIn() < System.currentTimeMillis() + 8000) {
            this.acquireToken();
        }
        String bearer = StringUtils.join("bearer", " ", this.authTokenDTO.getAccessToken());
        log.debug("token {}", bearer);
        return bearer;
    }

    private String buildOpenApiPath(@NonNull String path) {
        return StringUtils.join(this.host, StringUtils.stripStart(path, "/"));
    }

    /**
     * 计算sign
     *
     * @param sysParams
     */
    private String calcSign(HashMap<String, String> sysParams) {
        StringBuilder sb = new StringBuilder();
        List<String> forAlphaSort = new ArrayList<>(sysParams.keySet());
        //sign1: 首先将除 sign 外的所有参数名按照字母序的升序排列 RSA 签名，然后将 keyvalue 以如下形式进行拼接：key1=v1&key2=v2...，所生成的字符串即为待签名的
        log.debug("beforeSort {}", JSONUtil.toJsonStr(forAlphaSort));
        Collections.sort(forAlphaSort);
        log.debug("afterSort {}", JSONUtil.toJsonStr(forAlphaSort));
        for (String key : forAlphaSort) {
            String value = sysParams.get(key);
            if (StringUtils.isNotEmpty(value)) {
                sb.append(key).append('=').append(value).append("&");
            }
        }
        String signStr = StringUtils.stripEnd(sb.toString(), "&");
        log.debug("signStr {}", signStr);
        // sign2: 使用SHA256WithRSA 算法以及自己的私钥对第一步的结果进行RSA 签名，得到 byte 数组
        // sign3: 将第二步得到的 byte 数组进行Base64 编码
        sysParams.put("sign", RSAUtils.rsa2Sign(signStr, localRsaPrivateKey));
        return signStr;
    }

    public JSONObject postWrapper(String path, MediaType mediaType, Map<String, String> bizzBody) {
        //step1: 随机生成 128 bits 的 AES KEY 字符串（Base64 编码后为 24 长度字符串）
        final AESUtils aesUtils = new AESUtils();
        var sysParams = new HashMap<String, String>();
        sysParams.put("serviceSn", UUID.fastUUID().toString().replaceAll("-", ""));
        //step2: 业务参数转为字符串
        String bizzBodyStr = JSONUtil.toJsonStr(bizzBody);

        //step3: 对第二步的 JSON 字符串使用第一步生成的 Key 进行加密，具体算法为AES/ECB/PKCS5Padding，得到 byte 数组
        //step4: 对第三步的 byte 数组进行Base64 编码，得到一个字符串
        sysParams.put("encryptData", aesUtils.encryptData(bizzBodyStr));
        //step:5 对第一步生成的 KEY 使用对方提供的 RSA（1024 位）公钥进行加密，得到一个字符串，具体算法为 RSA/ECB/PKCS1Padding
        final byte[] aesKeyEncryptedBytes = remoteRsa.encrypt(aesUtils.getKey(), KeyType.PublicKey);
        sysParams.put("encryptKey", Base64.getEncoder().encodeToString(aesKeyEncryptedBytes));
        sysParams.put("requestTime", DateUtil.format(LocalDateTime.now(), "yyyyMMddHHmmss"));
        sysParams.put("source", SOURCE_CODE);
        sysParams.put("version", "1.0.0");
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(mediaType);
        headers.add("Authorization", this.getAuthorization());
        headers.add("companyNo", SOURCE_CODE);
        calcSign(sysParams);
        log.debug("sysParams {}", JSONUtil.toJsonStr(sysParams));
        HttpEntity<HashMap<String, String>> request = new HttpEntity<>(sysParams, headers);
        final ResponseEntity<String> responseEntity = restTemplate.postForEntity(buildOpenApiPath(path), request, String.class);
        return JSONObject.parseObject(responseEntity.getBody());
    }

    @Test
    void queryVehicleBasic() {
        final HashMap<String, String> bizzBody = Maps.newHashMap();
        bizzBody.put("epNumber", "1");
        bizzBody.put("emissionLevel", "10");
        bizzBody.put("mein", "10");
        bizzBody.put("fuelType", "0");
        bizzBody.put("productionDate", "2022-06-14");
        bizzBody.put("type", "2");
        bizzBody.put("useLocation", "2");
        final JSONObject jsonObject = this.postWrapper("/third/tdcr/mj/machine/info", MediaType.APPLICATION_JSON, bizzBody);
        System.out.println(jsonObject);
    }


    @Test
    void testCheckSign() throws Exception {
        final HashMap<String, String> hashMap = new HashMap<String, String>() {{
            put("e", "111");
            put("f", "111");
            put("g", "111");
            put("a", "asdfasdf");
            put("b", "111");
            put("d", "111");
            put("c", "111");
        }};
        final HashMap<String, String> hashMap1 = new HashMap<String, String>() {{
            put("e", "111");
            put("f", "111");
            put("g", "111");
            put("b", "111");
            put("d", "111");
            put("c", "111");
            put("a", "asdfasdf");
        }};
        String signString = calcSign(hashMap);
        String signString1 = calcSign(hashMap1);
        String sign = hashMap.get("sign");
        final boolean rsa2Check = RSAUtils.rsa2Check(signString1, localRsaPublicKey, sign);
        Assertions.assertTrue(rsa2Check);
    }


    @Test
    void testDecryptData() {
        final HashMap<String, String> hashMap = new HashMap<String, String>() {{
            put("e", "111");
            put("f", "111");
            put("g", "111");
            put("a", "asdfasdf");
            put("b", "111");
            put("d", "111");
            put("c", "111");
        }};
        AESUtils aesUtils = new AESUtils();
        final String encryptContent = aesUtils.encryptData(JSONUtil.toJsonStr(hashMap));
        final RSA mockRemoteRsa = new RSA("RSA/ECB/PKCS1Padding", localRsaPrivateKey, localRsaPublicKey);
        final String aesKeyEncrypt = mockRemoteRsa.encryptBase64(aesUtils.getKey(), KeyType.PublicKey);
        final byte[] aesKeyDecrypt = mockRemoteRsa.decrypt(aesKeyEncrypt, KeyType.PrivateKey);
        AESUtils aes = new AESUtils(aesKeyDecrypt);
//        final String decrypt = aes.decryptData(encryptContent);
//        System.out.println(decrypt);
    }

}
